//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.client.util;

import tv.dyndns.kishibe.client.Controller;

import com.google.gwt.user.client.Timer;

/**
 * Timerクラスを拡張し、一つのタイマーインスタンスで複数のタイマーを取り扱えるようにしたもの。
 * 
 * 複数のタイマーインスタンスを使用するとJavaScriptの実行環境によっては動作が遅くなってしまうが、インスタンスを一つに纏める事によって効率化・高速化することができる。参照URL:http://d.hatena.ne.jp/amachang/20060924/1159084608
 * 
 * また、実行環境によってタイマーの実行タイミングが変わってしまう現象を最小限に抑えることができる。
 * 
 * @author nodchip
 */
public abstract class SerialTimer extends Timer implements Comparable<SerialTimer> {
	public static final int MODE_AUTO = 0;
	public static final int MODE_NO_DELAY = 1;
	public static final int MODE_COOPERATION = 2;

	private static class Executor {
		private boolean scheduled = false;
		private final Timer timer = new Timer() {
			public void run() {
				execute();
			}
		};

		private void execute() {
			scheduled = false;
			SerialTimer.execute();
		}

		public void schedule(int time) {
			timer.schedule(time);
			scheduled = true;
		}

		public void cancel() {
			scheduled = false;
			timer.cancel();
		}

		public boolean isScheduled() {
			return scheduled;
		}
	}

	private static final Executor executor = new Executor();

	/**
	 * タスクを保持する木構造
	 */
	private static final Heap tasks = new Heap();
	/**
	 * 自動遅延フラグ
	 */
	private static boolean delay = false;

	private static int mode = MODE_AUTO;

	/**
	 * 登録されたタイマータスクを実行する
	 */
	private static void execute() {
		SerialTimer task = (SerialTimer) tasks.pop();

		if (task == null) {
			Controller.getInstance().showErrorMessage("空のタスクが実行されようとしました");
			executor.cancel();
			return;
		}

		// タイマータスクを実行済みとする
		task.isReserved = false;

		// タイマータスクがリピート設定されている場合は次のタスクを予約する
		if (task.duration > 0) {
			if (mode == MODE_AUTO && delay || mode == MODE_COOPERATION) {
				// タスクの協調を優先する場合はこちらを使用する
				task.executeTime = System.currentTimeMillis() + task.duration;
			} else {
				// タスクの遅延を許可しない場合はこちらを使用する
				task.executeTime += task.duration;
			}

			insertTask(task);
		}

		// タスクを実行する
		try {
			task.run();
		} catch (Exception e) {
			e.printStackTrace();
		}

		// 次に実行されるタスク
		SerialTimer nextTask = (SerialTimer) tasks.head();

		// タスクが登録されていない場合はタイマーを止める
		if (nextTask == null) {
			executor.cancel();
			return;
		}

		// 次のタイマータスクを実行するためのタイマーインスタンスを起動する
		int time = (int) (nextTask.executeTime - System.currentTimeMillis());
		delay = (time <= 0);
		time = Math.max(1, time);
		executor.schedule(time);
	}

	/**
	 * タイマータスクをタスクリストに予約する
	 * 
	 * @param t
	 *            予約するタイマータスク
	 */
	private static void insertTask(SerialTimer t) {
		if (t == null) {
			System.out.println("空のタイマータスクが挿入されようとしました");
			return;
		}

		if (t.isReserved) {
			t.cancel();
		}

		t.isReserved = true;
		tasks.push(t);

		// タイマーインスタンスが実行中でない場合、及びタスクリストの先頭にタイマータスクが挿入された場合にタイマーインスタンスを起動する
		if (!executor.isScheduled() || tasks.head() == t) {
			executor.cancel();

			int time = (int) (t.executeTime - System.currentTimeMillis());
			delay = (time <= 0);
			time = Math.max(1, time);
			executor.schedule(time);
		}
	}

	/**
	 * 動作モードを設定する
	 * 
	 * @param mode
	 *            動作モード
	 */
	public static void setMode(int mode) {
		SerialTimer.mode = mode;
	}

	/**
	 * 実行予定時刻
	 */
	private long executeTime;
	/**
	 * リピート間隔。 0の場合はリピートしないことを表す。
	 */
	private int duration = 0;
	/**
	 * タスクリストに予約されたかどうか。
	 */
	private boolean isReserved = false;
	private long id;
	private static long wholeId = 0;

	public SerialTimer() {
		id = wholeId++;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.user.client.Timer#schedule(int)
	 */
	public void schedule(int delayMillis) {
		cancel();
		duration = 0;
		executeTime = System.currentTimeMillis() + delayMillis;
		insertTask(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.user.client.Timer#scheduleRepeating(int)
	 */
	public void scheduleRepeating(int periodMillis) {
		cancel();
		duration = periodMillis;
		executeTime = System.currentTimeMillis() + periodMillis;
		insertTask(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.user.client.Timer#cancel()
	 */
	public void cancel() {
		// タスクリストから削除する
		if (isReserved) {
			isReserved = false;

			if (tasks.head() == this) {
				// 自分が先頭のタスクだった場合
				// 次のタスクを予約し直す
				tasks.remove(this);

				SerialTimer nextTask = (SerialTimer) tasks.pop();
				if (nextTask == null) {
					executor.cancel();
				} else {
					insertTask(nextTask);
				}
			} else {
				tasks.remove(this);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(SerialTimer o) {
		return executeTime != o.executeTime ? (int) (executeTime - o.executeTime) : (int) (id - o.id);
	}
}
